﻿using CLM.Gestao.Administrador.BLL;
using CLM.Gestao.Administrador.DAL;
using CLM.Gestao.Importacao;
using System;
using System.Collections.Generic;
using System.Linq;

namespace CLM.Gestao.Despesas.BLL
{
    /// <summary>
    /// 
    /// </summary>
    public class Despesa
    {
        #region Processos

        /// <summary>
        /// 
        /// </summary>
        private enum TipoProcesso
        {
            Despesas,
            OutrasDespesas
        }

        /// <summary>
        /// Sincroniza despesas
        /// </summary>
        /// <param name="pMes">Mês a ser sincronizado</param>
        /// <param name="pAno">Ano a ser sincronizado</param>
        public void SincronizarDespesas(int pMes, int pAno)
        {
            try
            {
                OnImportData(this, new ImportDataEventArgs("Acessando Banco de Dados Protheus..."));

                Protheus.BLL.SPDespesasGalderma ProcedureDespesas = new Protheus.BLL.SPDespesasGalderma();
                List<Lista> DespesasProtheus = (from p in ProcedureDespesas.ExecutaProcedure(pMes, pAno)
                                                select new Lista
                                                {
                                                    CodigoCentroCusto = p.CodigoCentroCusto,
                                                    CodigoContaContabil = p.CodigoContaContabil,
                                                    Data = p.Data,
                                                    DescricaoContaContabil = p.DescricaoContaContabil,
                                                    NomeCentroCusto = p.NomeCentroCusto,
                                                    Valor = (Convert.ToDecimal(p.Valor))
                                                }).ToList();

                ProcessarSincronizacaoDespesas(DespesasProtheus, pMes, pAno, TipoProcesso.Despesas);

                OnImportData(this, new ImportDataEventArgs("Sincronização efetuada com sucesso."));
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        /// <summary>
        /// Sincroniza outras despesas
        /// </summary>
        /// <param name="pMes">Mês a ser sincronizado</param>
        /// <param name="pAno">Ano a ser sincronizado</param>
        public void SincronizarOutrasDespesas(int pMes, int pAno)
        {
            try
            {
                OnImportData(this, new ImportDataEventArgs("Acessando Banco de Dados Protheus..."));

                // Altera a chamada para uma nova procedure 
                Protheus.BLL.SPOutrasDespesasGalderma ProcedureDespesas = new Protheus.BLL.SPOutrasDespesasGalderma();

                List<Lista> DespesasProtheus = (from p in ProcedureDespesas.ExecutaProcedure(pMes, pAno)
                                                select new Lista
                                                {
                                                    CodigoCentroCusto = p.CodigoCentroCusto,
                                                    CodigoContaContabil = p.CodigoContaContabil,
                                                    Data = p.Data,
                                                    DescricaoContaContabil = p.DescricaoContaContabil,
                                                    NomeCentroCusto = p.NomeCentroCusto,
                                                    Valor = (Convert.ToDecimal(p.Valor))
                                                }).ToList();

                ProcessarSincronizacaoDespesas(DespesasProtheus, pMes, pAno, TipoProcesso.OutrasDespesas);

                OnImportData(this, new ImportDataEventArgs("Sincronização efetuada com sucesso."));
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        /// <summary>
        /// Processa Sincronização de Despesas
        /// </summary>
        /// <param name="pListaDespesas">Lista de Despesas</param>
        /// <param name="pTipoProcesso">Tipo de processo a ser executado</param>
        private void ProcessarSincronizacaoDespesas(List<Lista> pListaDespesas, int pMes, int pAno, TipoProcesso pTipoProcesso)
        {
            /*
             * Este ID de Centro de Custo serve para alterar os Centros de
             * Custos de algumas despesas seguindo as regras de negócio
             */
            int IdCentroCustoAlteracao;
            CentroCusto Centro = new CentroCusto();
            if (pTipoProcesso == TipoProcesso.Despesas)
                IdCentroCustoAlteracao = Centro.GetItens(new CentroCusto.Lista() { NomeCentroCusto = "GERENCIA GERAL", CodigoCentroCusto = "02000001" }).First().IdCentroCusto;
            else
                IdCentroCustoAlteracao = Centro.GetItens(new CentroCusto.Lista() { NomeCentroCusto = "Other Operating Expenses / Income" }).First().IdCentroCusto;

            OnImportData(this, new ImportDataEventArgs("Validando dados..."));

            // Dictionary de Centro de Custo para recuperar o ID, utilizando o código
            CentroCusto CentroCusto = new CentroCusto();
            var CentrosCusto = CentroCusto.GetItens().ToDictionary(d => d.CodigoCentroCusto);

            // Dictionary de Conta Contábil para recuperar o ID, utilizando o código
            ContaContabil ContaContabil = new ContaContabil();
            var ContasContabeis = ContaContabil.GetItens(new ContaContabil.Lista() { IndicaGerencial = false }).ToDictionary(d => d.CodigoContaContabil);

            // Dictionary de Tipo de lancamewnto para recuperar o ID, utilizando o código
            LancamentoTipo lancTipo = new LancamentoTipo();
            var tipoLanc = lancTipo.GetItens().ToDictionary(d => d.DescricaoLancamentoTipo);


            // Período do mês de importação
            Periodo Periodo = new Periodo();
            var IdPeriodo = Periodo.GetItens(new Periodo.Lista() { AnoPeriodo = pAno, MesPeriodo = pMes }).First().IdPeriodo;

            ValidarDadosImportacao(CentrosCusto, ContasContabeis, pListaDespesas);

            OnImportData(this, new ImportDataEventArgs("Validação completa..."));

            ExcluirSincronizacoesAnteriores(pMes, pAno, pTipoProcesso);

            OnImportData(this, new ImportDataEventArgs("Iniciando processo..."));

            // Dictionary de Indicador Despesa para recuperar o ID, utilizando o nome
            IndicadorDespesa IndicadorDespesa = new IndicadorDespesa();
            var IdIndicadorDespesa = IndicadorDespesa.GetLista(new IndicadorDespesa.Lista()).ToDictionary(d => d.NomeIndicadorDespesa);

            var Lancamentos = (from d in pListaDespesas
                               select new TB_LANCAMENTO
                               {
                                   DATA_INCLUSAO = DateTime.Now,
                                   DESCRICAO_LANCAMENTO = string.Format("Saldo de despesas do período {0}", d.Data),
                                   DATA_LANCAMENTO = new DateTime(pAno, pMes, 1),
                                   ID_CENTRO_CUSTO = CentrosCusto[d.CodigoCentroCusto.PadRight(8, '0')].IdCentroCusto,
                                   ID_EVENTO = // Utilizando esta prop para mexer o mínimo na classe - Jota - 19/05
                                        pTipoProcesso == TipoProcesso.Despesas && d.CategoriaCentroCusto != "Hort" &&
                                        (d.CodigoContaContabil.Contains("21019") || d.CodigoContaContabil.Contains("25003")) ?
                                        IdCentroCustoAlteracao :
                                        pTipoProcesso == TipoProcesso.OutrasDespesas ?
                                        IdCentroCustoAlteracao : 0,
                                   INDICA_DESPESA_COMPROMETIDA = (d.CodigoContaContabil.Contains("21019") || d.CodigoContaContabil.Contains("25003")), // // Utilizando esta prop para mexer o mínimo na classe - Jota - 19/05
                                   ID_CONTA_CONTABIL = ContasContabeis[d.CodigoContaContabil].IdContaContabil,
                                   ID_LANCAMENTO_TIPO = tipoLanc["Real"].IdLancamentoTipo,
                                   ID_PERIODO = IdPeriodo,
                                   VALOR_LANCAMENTO = Convert.ToDecimal(d.Valor),
                                   TB_LANCAMENTO_DESPESA = new TrackableCollection<TB_LANCAMENTO_DESPESA>()
                                  {
                                       new TB_LANCAMENTO_DESPESA()
                                       {
                                           ID_INDICADOR_DESPESA = (int)IdIndicadorDespesa[pTipoProcesso == TipoProcesso.Despesas ? Indicadores.IndicadoresDespesas.Despesas : Indicadores.IndicadoresDespesas.Other].IdIndicadorDespesa
                                       }
                                  }
                               }).ToList();

            Lancamento Lancamento = new Lancamento();
            int i = 0;
            foreach (var item in Lancamentos)
            {
                Lancamento.Add(item);
                if ((bool?)(item.INDICA_DESPESA_COMPROMETIDA) == true)
                {
                    // Adiciona o ajuste gerencial
                    Lancamento.Add(new TB_LANCAMENTO()
                    {
                        DATA_INCLUSAO = DateTime.Now,
                        DESCRICAO_LANCAMENTO = "Ajuste - " + item.DESCRICAO_LANCAMENTO,
                        DATA_LANCAMENTO = new DateTime(pAno, pMes, 1),
                        ID_CENTRO_CUSTO = item.ID_EVENTO,
                        ID_CONTA_CONTABIL = item.ID_CONTA_CONTABIL,
                        ID_LANCAMENTO_TIPO = tipoLanc["Ajuste Gerencial"].IdLancamentoTipo,
                        ID_PERIODO = IdPeriodo,
                        VALOR_LANCAMENTO = item.VALOR_LANCAMENTO,
                        TB_LANCAMENTO_DESPESA = new TrackableCollection<TB_LANCAMENTO_DESPESA>()
                                  {
                                       new TB_LANCAMENTO_DESPESA()
                                       {
                                           ID_INDICADOR_DESPESA = (int)IdIndicadorDespesa[pTipoProcesso == TipoProcesso.Despesas ? Indicadores.IndicadoresDespesas.Despesas : Indicadores.IndicadoresDespesas.Other].IdIndicadorDespesa
                                       }
                                  }
                    });

                    //Adiciona o valor negativo
                    Lancamento.Add(new TB_LANCAMENTO()
                    {
                        DATA_INCLUSAO = DateTime.Now,
                        DESCRICAO_LANCAMENTO = "Ajuste - " + item.DESCRICAO_LANCAMENTO,
                        DATA_LANCAMENTO = new DateTime(pAno, pMes, 1),
                        ID_CENTRO_CUSTO = item.ID_CENTRO_CUSTO,
                        ID_CONTA_CONTABIL = item.ID_CONTA_CONTABIL,
                        ID_LANCAMENTO_TIPO = tipoLanc["Ajuste Gerencial"].IdLancamentoTipo,
                        ID_PERIODO = IdPeriodo,
                        VALOR_LANCAMENTO = item.VALOR_LANCAMENTO * (-1),
                        TB_LANCAMENTO_DESPESA = new TrackableCollection<TB_LANCAMENTO_DESPESA>()
                                  {
                                       new TB_LANCAMENTO_DESPESA()
                                       {
                                           ID_INDICADOR_DESPESA = (int)IdIndicadorDespesa[pTipoProcesso == TipoProcesso.Despesas ? Indicadores.IndicadoresDespesas.Despesas : Indicadores.IndicadoresDespesas.Other].IdIndicadorDespesa
                                       }
                                  }
                    });
                }
                item.ID_EVENTO = null;
                i++;
                OnImportData(this, new ImportDataEventArgs("Sincronizando...", i, Lancamentos.Count()));
            }
            Lancamento.Save();
        }

        /// <summary>
        /// Valida Dados de Importação
        /// </summary>
        /// <param name="pCentrosCusto">Centros de Custo</param>
        /// <param name="pContasContabeis">Contas Contábeis</param>
        /// <param name="pListaDespesas">Lista de Despesas</param>
        private void ValidarDadosImportacao(Dictionary<string, CentroCusto.Itens> pCentrosCusto, Dictionary<string, ContaContabil.Itens> pContasContabeis, List<Lista> pListaDespesas)
        {
            var Valida = (from p in pListaDespesas
                          join db in pCentrosCusto.Select(s => s.Value).ToList()
                          on p.CodigoCentroCusto equals db.CodigoCentroCusto
                          into centros
                          from db in centros.DefaultIfEmpty()
                          where db == null
                          select p).Count();

            if (Valida > 0)
                throw new Exception("Existem despesas de centros de custo não cadastrados no sistema.\nPor favor, execute a sincronização de centros de custo antes de sincronizar as despesas.");

            Valida = (from p in pListaDespesas
                      join db in pContasContabeis.Select(s => s.Value).ToList()
                      on p.CodigoContaContabil equals db.CodigoContaContabil
                      into contas
                      from db in contas.DefaultIfEmpty()
                      where db == null
                      select p).Count();

            if (Valida > 0)
                throw new Exception("Existem despesas de contas contábeis não cadastradas no sistema.\nPor favor, execute a sincronização de contas contábeis antes de sincronizar as despesas.");
        }

        /// <summary>
        /// Executa a limpeza da tabela de lançamentos para iniciar
        /// uma nova sincronização sem duplicação de dados
        /// </summary>
        /// <param name="pMes">Mês para filtro dos lançamentos</param>
        /// <param name="pAno">Ano para filtro dos lançamentos</param>
        /// <param name="pTipoProcesso">Tipo de lançamento a ser excluído</param>
        private void ExcluirSincronizacoesAnteriores(int pMes, int pAno, TipoProcesso pTipoProcesso)
        {
            OnImportData(this, new ImportDataEventArgs("Excluindo lançamentos anteriores..."));

            // Período do mês de importação
            Periodo Periodo = new Periodo();
            var IdPeriodo = Periodo.GetItens(new Periodo.Lista() { AnoPeriodo = pAno, MesPeriodo = pMes }).First().IdPeriodo;

            // Dictionary de Indicador Despesa para recuperar o ID, utilizando o nome
            IndicadorDespesa IndicadorDespesa = new IndicadorDespesa();
            var IdIndicadorDespesa = IndicadorDespesa.GetLista(new IndicadorDespesa.Lista()).ToDictionary(d => d.NomeIndicadorDespesa);

            SPExcluirLancamentosAnterioresDespesas ExcluirLancamentosAnterioresDespesas = new SPExcluirLancamentosAnterioresDespesas();
            ExcluirLancamentosAnterioresDespesas.Execute(IdPeriodo, (int)IdIndicadorDespesa[pTipoProcesso == TipoProcesso.Despesas ? Indicadores.IndicadoresDespesas.Despesas : Indicadores.IndicadoresDespesas.Other].IdIndicadorDespesa);
        }

        #endregion Processos

        #region Relatório de Indicadores de Despesas

        /// <summary>
        /// Relatório de Indicadores de Despesas
        /// </summary>
        /// <param name="pAno">Ano a ser exportado</param>
        /// <returns>Lista</returns>
        public List<Lista> GerarRelatorioDespesas(int pAno, bool pIndicaRelatorioGerencial = true)
        {
            Lancamento Lancamento = new Lancamento();
            LancamentoDespesa LancamentoDespesa = new LancamentoDespesa();

            // Dictionary de tipos de lançamento para recuperar o ID, utilizando o nome
            LancamentoTipo LancamentoTipo = new LancamentoTipo();
            var TiposLancamento = LancamentoTipo.GetLista().ToDictionary(d => d.DescricaoLancamentoTipo);

            //List<Lista> Despesas = (from l in Lancamento.GetListaRelatorio(new Lancamento.Lista() { IndicaGerencial = false, Ano = pAno, IdLancamentoTipo = TiposLancamento["Real"].IdLancamentoTipo })
            List<Lista> Despesas = (from l in Lancamento.GetListaRelatorio(new Lancamento.Lista() { IndicaGerencial = false, Ano = pAno, IndicaRelatorioGerencial = pIndicaRelatorioGerencial })
                                    group l by new
                                     {
                                         CodigoCentroCusto = l.CodigoCentroCusto,
                                         CodigoContaContabil = l.CodigoContaContabil,
                                     } into gr
                                    select new Lista
                                    {
                                        CodigoCentroCusto = gr.Key.CodigoCentroCusto,
                                        CodigoContaContabil = gr.Key.CodigoContaContabil,
                                        DescricaoContaContabil = gr.First().DescricaoContaContabil,
                                        CategoriaCentroCusto = gr.First().CategoriaCentroCusto,
                                        CategoriaContaContabil = gr.First().CategoriaContaContabil,
                                        NomeCentroCusto = gr.First().NomeCentroCusto,
                                        Valor = gr.Select(s => s.ValorLancamento).Sum(),
                                        ValorYTD = gr.Where(w => ((DateTime)w.DataLancamento).Month <= System.DateTime.Now.Month).Select(s => s.ValorLancamento).Sum(),
                                        ValorJan = gr.Where(w => ((DateTime)w.DataLancamento).Month == 1).Select(s => s.ValorLancamento).Sum(),
                                        ValorFev = gr.Where(w => ((DateTime)w.DataLancamento).Month == 2).Select(s => s.ValorLancamento).Sum(),
                                        ValorMar = gr.Where(w => ((DateTime)w.DataLancamento).Month == 3).Select(s => s.ValorLancamento).Sum(),
                                        ValorAbr = gr.Where(w => ((DateTime)w.DataLancamento).Month == 4).Select(s => s.ValorLancamento).Sum(),
                                        ValorMai = gr.Where(w => ((DateTime)w.DataLancamento).Month == 5).Select(s => s.ValorLancamento).Sum(),
                                        ValorJun = gr.Where(w => ((DateTime)w.DataLancamento).Month == 6).Select(s => s.ValorLancamento).Sum(),
                                        ValorJul = gr.Where(w => ((DateTime)w.DataLancamento).Month == 7).Select(s => s.ValorLancamento).Sum(),
                                        ValorAgo = gr.Where(w => ((DateTime)w.DataLancamento).Month == 8).Select(s => s.ValorLancamento).Sum(),
                                        ValorSet = gr.Where(w => ((DateTime)w.DataLancamento).Month == 9).Select(s => s.ValorLancamento).Sum(),
                                        ValorOut = gr.Where(w => ((DateTime)w.DataLancamento).Month == 10).Select(s => s.ValorLancamento).Sum(),
                                        ValorNov = gr.Where(w => ((DateTime)w.DataLancamento).Month == 11).Select(s => s.ValorLancamento).Sum(),
                                        ValorDez = gr.Where(w => ((DateTime)w.DataLancamento).Month == 12).Select(s => s.ValorLancamento).Sum()
                                    }).ToList();
            return Despesas;
        }

        public List<Lista> GerarRelatorioRateioDespesas(int pAno, int pIdPeriodo)
        {
            Lancamento Lancamento = new Lancamento();
            LancamentoDespesa LancamentoDespesa = new LancamentoDespesa();

            // Dictionary de tipos de lançamento para recuperar o ID, utilizando o nome
            LancamentoTipo LancamentoTipo = new LancamentoTipo();
            var TiposLancamento = LancamentoTipo.GetLista().ToDictionary(d => d.DescricaoLancamentoTipo);

            List<Lista> Despesas = (from l in Lancamento.GetListaRelatorio(new Lancamento.Lista() { IndicaGerencial = false, Ano = pAno, IdPeriodo = pIdPeriodo, IdLancamentoTipo = TiposLancamento["Real"].IdLancamentoTipo, DescricaoLancamento = "Rateio" })
                                    group l by new
                                    {
                                        CodigoCentroCusto = l.CodigoCentroCusto,
                                        NomeCentroCusto = l.NomeCentroCusto,
                                        CodigoProdutoGlobal = l.CodigoProdutoGlobal,
                                        NomeProdutoGlobal = l.NomeProdutoGlobal,
                                        DescricaoLancamento = l.DescricaoLancamento
                                    } into gr
                                    orderby gr.Key.DescricaoLancamento
                                    select new Lista
                                    {
                                        CodigoCentroCusto = gr.Key.CodigoCentroCusto,
                                        NomeCentroCusto = gr.Key.NomeCentroCusto,
                                        CodigoProduto = gr.Key.CodigoProdutoGlobal,
                                        NomeProduto = gr.Key.NomeProdutoGlobal,
                                        DescricaoLancamento = gr.Key.DescricaoLancamento,
                                        Valor = gr.Sum(s => s.ValorLancamento)
                                    }).ToList();
            return Despesas;
        }

        #endregion Relatório de Indicadores de Despesas

        #region Classes

        /// <summary>
        /// Lista com as propriedades da classe
        /// </summary>
        public class Lista
        {
            public string CodigoCentroCusto { get; set; }
            public string NomeCentroCusto { get; set; }
            public string CategoriaCentroCusto { get; set; }
            public string CodigoContaContabil { get; set; }
            public string DescricaoContaContabil { get; set; }
            public string CategoriaContaContabil { get; set; }
            public string CodigoProduto { get; set; }
            public string NomeProduto { get; set; }
            public string DescricaoLancamento { get; set; }
            public string TipoRateio { get; set; }
            public string Data { get; set; }
            public decimal? ValorJan { get; set; }
            public decimal? ValorFev { get; set; }
            public decimal? ValorMar { get; set; }
            public decimal? ValorAbr { get; set; }
            public decimal? ValorMai { get; set; }
            public decimal? ValorJun { get; set; }
            public decimal? ValorJul { get; set; }
            public decimal? ValorAgo { get; set; }
            public decimal? ValorSet { get; set; }
            public decimal? ValorOut { get; set; }
            public decimal? ValorNov { get; set; }
            public decimal? ValorDez { get; set; }
            public decimal? Valor { get; set; }
            public decimal? ValorYTD { get; set; }
        }

        #endregion Classes

        public event ImportDataEventHandler OnImportData;
    }
}